package org.panxiong.openapi.handler;

import org.apache.maven.plugin.AbstractMojo;
import org.panxiong.openapi.model.FieldModel;
import org.panxiong.openapi.model.MappingMode;
import org.panxiong.openapi.utils.ClassUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ControllerBase {

    // 上下文
    protected AbstractMojo context;

    protected void setMappingModel(MappingMode model, Annotation mapping)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> mappingClass = mapping.annotationType();

        // name
        Method nameMethod = mappingClass.getDeclaredMethod("name");
        String name = (String) nameMethod.invoke(mapping);
        model.setName(name);

        // value / path
        Method valueMethod = mappingClass.getDeclaredMethod("value");
        String[] value = (String[]) valueMethod.invoke(mapping);
        model.setPaths(Arrays.asList(value));
        if (model.getPaths() == null || model.getPaths().size() <= 0) {
            Method pathMethod = mappingClass.getDeclaredMethod("path");
            String[] path = (String[]) pathMethod.invoke(mapping);
            model.setPaths(Arrays.asList(path));
        }

        // method
        if (mapping instanceof RequestMapping) {
            Method methodMethod = mappingClass.getDeclaredMethod("method");
            Object method = methodMethod.invoke(mapping);
            if (method instanceof RequestMethod[]) {
                List<String> list = new ArrayList<>();
                for (RequestMethod requestMethod : (RequestMethod[]) method) {
                    list.add(requestMethod.name());
                }
                model.setMethods(list);
            }
        } else if (mapping instanceof GetMapping) {
            model.setMethods(Collections.singletonList(RequestMethod.GET.name()));
        } else if (mapping instanceof PostMapping) {
            model.setMethods(Collections.singletonList(RequestMethod.POST.name()));
        } else if (mapping instanceof PutMapping) {
            model.setMethods(Collections.singletonList(RequestMethod.PUT.name()));
        } else if (mapping instanceof PatchMapping) {
            model.setMethods(Collections.singletonList(RequestMethod.PATCH.name()));
        } else if (mapping instanceof DeleteMapping) {
            model.setMethods(Collections.singletonList(RequestMethod.DELETE.name()));
        } else {
            model.setMethods(Collections.singletonList(RequestMethod.OPTIONS.name()));  // HEAD, TRACE
        }
    }

    // ClassMapping
    protected MappingMode buildMethodModelClassMapping(Method cls) throws Exception {
        MappingMode cmm = new MappingMode();
        RequestMapping requestMapping = cls.getAnnotation(RequestMapping.class);
        if (requestMapping != null) {
            setMappingModel(cmm, requestMapping);
        }
        GetMapping getMapping = cls.getAnnotation(GetMapping.class);
        if (getMapping != null) {
            setMappingModel(cmm, getMapping);
        }
        PostMapping postMapping = cls.getAnnotation(PostMapping.class);
        if (postMapping != null) {
            setMappingModel(cmm, postMapping);
        }
        PutMapping putMapping = cls.getAnnotation(PutMapping.class);
        if (putMapping != null) {
            setMappingModel(cmm, putMapping);
        }
        PatchMapping patchMapping = cls.getAnnotation(PatchMapping.class);
        if (patchMapping != null) {
            setMappingModel(cmm, patchMapping);
        }
        DeleteMapping deleteMapping = cls.getAnnotation(DeleteMapping.class);
        if (deleteMapping != null) {
            setMappingModel(cmm, deleteMapping);
        }
        return cmm;
    }

    protected String getMethodContentType(Method method) {
        String contentType = "application/x-www-form-urlencoded";
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
            if (requestBody != null) {
                contentType = "application/json";
            }
            RequestPart requestPart = parameter.getAnnotation(RequestPart.class);
            if (requestPart != null) {
                contentType = "multipart/form-data";
            }
        }
        return contentType;
    }

    protected void buildFieldWithChild(List<FieldModel> fieldModels, Class<?> type) {
        Field[] fields = type.getDeclaredFields();
        for (Field field : fields) {
            FieldModel model = new FieldModel();
            model.setVarName(field.getName());
            model.setDateType(field.getType().getSimpleName());
            model.setChildren(new ArrayList<>());

            if (ClassUtils.isCustomModel(field.getType())) {
                buildFieldWithChild(model.getChildren(), field.getType());
            }

            fieldModels.add(model);
        }
    }

    protected FieldModel handlerTypeRequestBody(Parameter parameter, RequestBody requestBody) {
        FieldModel info = new FieldModel();
        info.setVarName(parameter.getName());
        info.setMappingName(null);
        info.setRequired(requestBody.required());
        info.setStruct(new ArrayList<>());

        // 递归遍历所有嵌套对象
        buildFieldWithChild(info.getStruct(), parameter.getType());

        try {
            info.setExample(ClassUtils.newDefaultInstance(parameter.getType()));
        } catch (Exception e) {
            context.getLog().error("创建RequestBody示例数据错误", e);
        }

        return info;
    }

    protected String getAnnotationValue(Annotation annotation, String defaultValue) {
        try {
            String result, target = "value", backup = "name";
            Class<?> cls = annotation.annotationType();
            Method valueMethod = cls.getDeclaredMethod(target);
            String value = (String) valueMethod.invoke(annotation);
            if (!ClassUtils.isEmpty(backup) && ClassUtils.isEmpty(value)) {
                Method pathMethod = cls.getDeclaredMethod(backup);
                result = (String) pathMethod.invoke(annotation);
            } else {
                result = value;
            }
            if (ClassUtils.isEmpty(result)) {
                return defaultValue;
            } else {
                return value;
            }
        } catch (Exception e) {
            context.getLog().error("读取Annotation参数错误", e);
            return defaultValue;
        }
    }

    protected FieldModel handlerTypeModelAttribute(Parameter parameter, ModelAttribute modelAttribute) {
        FieldModel info = new FieldModel();
        info.setVarName(parameter.getName());
        info.setMappingName(getAnnotationValue(modelAttribute, ""));
        info.setRequired(modelAttribute.binding());  // binding ！= required
        info.setStruct(new ArrayList<>());

        // 递归遍历所有嵌套对象
        buildFieldWithChild(info.getStruct(), parameter.getType());

        try {
            StringBuilder sb = new StringBuilder();
            Object instance = ClassUtils.newDefaultInstance(parameter.getType());
            Field[] fields = instance.getClass().getDeclaredFields();
            for (Field field : fields) {
                buildModelAttributeExample(sb, instance, field, "");
            }
            String example = sb.toString();
            if (example.endsWith("&")) {
                example = example.substring(0, example.length() - 1);
            }
            info.setExample(example);
        } catch (Exception e) {
            context.getLog().error("创建ModelAttribute示例数据错误", e);
        }

        return info;
    }

    protected void buildModelAttributeExample(StringBuilder sb, Object instance, Field field, String prefix) throws IllegalAccessException {
        field.setAccessible(true);
        if (ClassUtils.isCustomModel(field.getType())) {
            Object fieldInstance = field.get(instance);
            Field[] declaredFields = field.getType().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                buildModelAttributeExample(sb, fieldInstance, declaredField, field.getName());
            }
        } else {
            if (!ClassUtils.isEmpty(prefix)) {
                sb.append(prefix).append(".");
            }
            sb.append(field.getName()).append("=").append(field.get(instance)).append("&");
        }
    }
}
